package com.durian.base.rxhttp.await

import com.durian.base.rxhttp.RxHttp
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import kotlin.coroutines.CoroutineContext

/**
 * 说明：IAwait
 * <p/>
 * 作者：Fanly
 * <p/>
 * 类型：Class
 * <p/>
 * 时间：2020/04/25 16:45
 * <p/>
 * 版本：version 1.0
 */
interface IAwait<T> {
    suspend fun await(): T
}

inline fun <T, R> IAwait<T>.newAwait(
    crossinline block: suspend IAwait<T>.() -> R
): IAwait<R> = object : IAwait<R> {
    override suspend fun await(): R {
        return block.invoke(this@newAwait)
    }
}

fun <T> IAwait<T>.flowOn(
    context: CoroutineContext
): IAwait<T> = newAwait {
    withContext(context) { await() }
}

fun <T> IAwait<T>.asFlow(): Flow<T> = flow{ emit(await()) }

/**
 * 请求结束后延迟
 * @param delay 延迟时间
 */
fun <T> IAwait<T>.afterDelay(
    delay: Long
): IAwait<T> = AwaitAfterDelay(this, delay)

/**
 * 延迟发送请求
 * @param delay 延迟时间
 */
fun <T, R> IAwait<T>.map(
    block: (T?) -> R
): IAwait<R> = AwaitMap(this, block)

/**
 * 开始延迟后请求
 * @param delay 延迟时间
 */
fun <T> IAwait<T>.startDelay(
    delay: Long
): IAwait<T> = AwaitStartDelay(this, delay)

/**
 * 失败重试，该方法仅在协程中使用才有效
 * @param times 重试次数，默认重复无限次
 * @param period 重试周期，默认为0
 * @param test 重试条件，默认无条件
 */
fun <T> IAwait<T>.retry(
    times: Int = Int.MAX_VALUE,
    period: Long = 0L,
    test: ((Throwable) -> Boolean)? = null
): IAwait<T> = AwaitRetry(this, times, period, test)

/**
 * 为单个请求设置超时时长，该方法仅在协程中使用才有效
 * @param timeoutMillis 超时时间
 */
fun <T> IAwait<T>.timeout(
    timeoutMillis: Long
): IAwait<T> = AwaitTimeout(this, timeoutMillis)

/**
 * 请求异常默认返回值
 */
fun <T> IAwait<T>.errorReturn(
    map: (Throwable) -> T
): IAwait<T> = AwaitErrorReturn(this, map)

suspend fun <T> IAwait<T>.async() = supervisorScope {
    async(Dispatchers.IO) { await() }
}

suspend fun <T> IAwait<T>.tryAwait(onCatch: ((Throwable) -> Unit)? = null): T? {
    return try {
        async().await()
    } catch (e: Throwable) {
        supervisorScope {
            launch(Dispatchers.Main) {
                when {
                    onCatch != null -> {
                        onCatch.invoke(e)
                    }
                    RxHttp.getErrorHandler() != null -> {
                        RxHttp.getErrorHandler()?.invoke(e)
                    }
                }
            }
        }
        null
    }
}
